﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using DVBViMon.Utils;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.Unity;

namespace DVBViMon
{
    static class Program
    {
        // do not change this string, it must be the same as the Notification Icon Text string!
        private const string NotifyIconText = "DVBViewer iMon Display Tool";

        /// <summary>
        /// Der Haupteinstiegspunkt für die Anwendung.
        /// </summary>
        [STAThread]
        static void Main()
        {

            if (!CheckApplicationAlreadyRunning(true))
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);

                // build unity container
                IUnityContainer container = new UnityContainer();

                // register logging service
                container.RegisterType<Logger<LogEntry>>(new ContainerControlledLifetimeManager());
                // register main application form
 //               container.RegisterType<FormMain>(new ContainerControlledLifetimeManager());

                // and tell Enterprise Library to use it
                EnterpriseLibraryContainer.Current = new UnityServiceLocator(container);

//                Application.Run(container.Resolve<FormMain>());
                Application.Run(new FormMain());
            }
        }

        #region Methods needed if Application Already Running
        /// <summary>
        /// Checks whether another instance of the same application is already running.
        /// </summary>
        /// <param name="switchToAlreadyRunningProcess">Whether the already running process is flashed and brought to front.</param>
        /// <returns>Whether another instance of the application is already running.</returns>
        public static bool CheckApplicationAlreadyRunning(bool switchToAlreadyRunningProcess)
        {
            List<Process> processes = new List<Process>();
            string processName = Process.GetCurrentProcess().ProcessName;
            if (processName.Contains("vshost")) // check for debugging process in Visual Studio
            {
                string processBaseName = processName.Replace(".vshost", "");
                processes = (Process.GetProcessesByName(processBaseName)).ToList();
            }
            processes.AddRange( (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName)).ToList()  );

            // > 1 because if there is already a process running, then 2 instances (with this one) are present
            if (processes.Count > 1)
            {
                if (switchToAlreadyRunningProcess)
                {
                    IntPtr hwnd = processes[0].Id != Process.GetCurrentProcess().Id ? processes[0].MainWindowHandle : processes[1].MainWindowHandle;

                    if (hwnd == IntPtr.Zero)
                        RestoreFromTray();
                    else
                    {
                        if (!IsWindowVisible(hwnd))
                        {
                            ShowWindowAsync(hwnd, SW_SHOW);
                                // restore window, not working, if it is in the system notification area, then hwnd is 0
                        }

                        SwitchToThisWindow(hwnd, true);
                    }
                }

                return true;
            }

            return false;
        }

        /// <summary>
        /// Shows application if it is minimized into the system tray.
        /// </summary>
        private static void RestoreFromTray()
        {
            IntPtr hTray = User32.FindWindowEx(IntPtr.Zero, IntPtr.Zero, "Shell_TrayWnd", null);
            IntPtr hTrayNotify = User32.FindWindowEx(hTray, IntPtr.Zero, "TrayNotifyWnd", null);
            IntPtr hSysPager = User32.FindWindowEx(hTrayNotify, IntPtr.Zero, "SysPager", null);
            IntPtr hToolbar = User32.FindWindowEx(hSysPager, IntPtr.Zero, "ToolbarWindow32", null);

            if (hToolbar == IntPtr.Zero)
                return;

            // get number of icons in the system tray
            UInt32 count = User32.SendMessage(hToolbar, TB.BUTTONCOUNT, 0, 0);

            // check OS 32 or 64 bit
            bool os64Bit = Environment.Is64BitOperatingSystem;
            if (os64Bit)
            {
                NotifyIconData_WoW64 notifyIconData = new NotifyIconData_WoW64();
                for (int i = 0; i < count; ++i)
                {
                    string iconText = string.Empty;
                    // find handle for all Icons, data is in notifIconData
                    bool success = GetTBButton_WoW64(hToolbar, i, ref iconText, notifyIconData);
                    // compare icon text to identify our icon
                    if (iconText == NotifyIconText)
                    {
                        // send a double click to our icon to show our window
                        User32.PostMessage(notifyIconData.hWnd, (UInt32)notifyIconData.uCallbackMessage, notifyIconData.uID, WM_LBUTTONDBLCLK);
                        return;
                    }
                }
            }
            else
            {
                NotifyIconData_32 notifyIconData = new NotifyIconData_32();
                for (int i = 0; i < count; ++i)
                {
                    string iconText = string.Empty;
                    bool success = GetTBButton_32(hToolbar, i, ref iconText, notifyIconData);
                    if (iconText == NotifyIconText)
                    {
                        User32.PostMessage(notifyIconData.hWnd, notifyIconData.uCallbackMessage, notifyIconData.uID, WM_LBUTTONDBLCLK);
                        return;
                    }
                }          
            }

        }


        /// <summary>
        /// Gets the Tray Icon as button, x86 version.
        /// </summary>
        /// <param name="hToolbar">The htoolbar.</param>
        /// <param name="i">Icon number .</param>
        /// <param name="iconText">The icon text returned.</param>
        /// <param name="notifyIconData">The notify icon data with handle and callback returned.</param>
        /// <returns> true if no errors</returns>
        private static bool GetTBButton_32(IntPtr hToolbar, int i, ref string iconText, NotifyIconData_32 notifyIconData)
        {
            // One page
            const int BUFFER_SIZE = 0x1000;

            UInt32 processId;
            UInt32 threadId = User32.GetWindowThreadProcessId(hToolbar, out processId);

            // open process which owns hToolbar
            IntPtr hProcess = Kernel32.OpenProcess(ProcessRights.ALL_ACCESS, false, processId);
            if (hProcess == IntPtr.Zero)
            {
                Debug.Print("OpenProcess failed.");
                return false;
            }

            // create a memory space for the remote process which owns the toolbar (... explorer)
            IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx(
                hProcess,
                IntPtr.Zero,
                new UIntPtr(BUFFER_SIZE),
                MemAllocationType.COMMIT,
                MemoryProtection.PAGE_READWRITE);

            if (ipRemoteBuffer == IntPtr.Zero)
            {
                Debug.Print("VirtualAllocEx failed.");
                return false;
            }

            // get button i
            int bb = (int)User32.SendMessage(hToolbar, TB.GETBUTTON, (IntPtr)i, ipRemoteBuffer);
            if (bb == 0)
            {
                Debug.Print("SendMessage TB.GETBUTTON");
                return (false);
            }

            TBBUTTON_32 tb32 = new TBBUTTON_32();
            Int32 size = tb32.Size();
            IntPtr tb32Buffer = Marshal.AllocHGlobal(size);
            IntPtr bytesRead = Marshal.AllocHGlobal(4);

            // copy the TBBUTTON data from remote buffer to local process memory
            bool bb2 = Kernel32.ReadProcessMemory(
                hProcess,
                ipRemoteBuffer,
                tb32Buffer,
                new UIntPtr((uint)size),
                bytesRead);

            if (!bb2)
            {
                Debug.Print("ReadProcessMemory");
                return false;
            }

            // copy unmanaged data to managed ( class )
            Marshal.PtrToStructure(tb32Buffer, tb32);

            // free resources
            if (tb32Buffer != IntPtr.Zero)
            {
                // Free the memory allocated previously by AllocHGlobal.
                Marshal.FreeHGlobal(tb32Buffer);
            }

            if (bytesRead != IntPtr.Zero)
            {
                // Free the memory allocated previously by AllocHGlobal.
                Marshal.FreeHGlobal(bytesRead);
            }

            // find button text
            int textCharCount = (int)User32.SendMessage(hToolbar, TB.GETBUTTONTEXTW, (IntPtr)tb32.idCommand, ipRemoteBuffer);
            if (textCharCount == -1)
            {
                Debug.Print("SendMessage TB.GETBUTTONTEXTW");
                return false;
            }

            IntPtr charBuffer = Marshal.AllocHGlobal(BUFFER_SIZE);
            bytesRead = Marshal.AllocHGlobal(4);

            bool bb4 = Kernel32.ReadProcessMemory(
                 hProcess,
                 ipRemoteBuffer,
                 charBuffer,
                 new UIntPtr(BUFFER_SIZE),
                 bytesRead);

            if (!bb4)
            {
                Debug.Print("ReadProcessMemory button text");
                return false;
            }

            // this is the button text which is shown when hoovering over the button/Icon
            iconText = Marshal.PtrToStringUni(charBuffer, textCharCount);
            if (iconText == " ") iconText = String.Empty;

            if (charBuffer != IntPtr.Zero)
            {
                // Free the memory allocated previously by AllocHGlobal.
                Marshal.FreeHGlobal(charBuffer);
            }

            if (bytesRead != IntPtr.Zero)
            {
                // Free the memory allocated previously by AllocHGlobal.
                Marshal.FreeHGlobal(bytesRead);
            }

            // get now handle, callback etc for the button, the data structure is undocumented
            IntPtr nidData = Marshal.AllocHGlobal(notifyIconData.Size());
            IntPtr remoteData = new IntPtr(tb32.dwData);
            bytesRead = Marshal.AllocHGlobal(4);

            bool bbb4 = Kernel32.ReadProcessMemory(
                hProcess,
                remoteData,
                nidData,
                new UIntPtr((uint)notifyIconData.Size()),
                bytesRead);

            if (!bbb4)
            {
                Debug.Print("ReadProcessMemory dwData");
                return false;
            }

            Marshal.PtrToStructure(nidData, notifyIconData);

            if (nidData != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(nidData);
            }

            if (bytesRead != IntPtr.Zero)
            {
                // Free the memory allocated previously by AllocHGlobal.
                Marshal.FreeHGlobal(bytesRead);
            }

            // release resources
            Kernel32.VirtualFreeEx(
                hProcess,
                ipRemoteBuffer,
                UIntPtr.Zero,
                MemAllocationType.RELEASE);

            Kernel32.CloseHandle(hProcess);

            return true; 
        }


        /// <summary>
        /// Gets the TB button if OS is 64 bit (WoW64).
        /// </summary>
        /// <param name="hToolbar">The htoolbar.</param>
        /// <param name="i">The icon i.</param>
        /// <param name="iconText">The icon text returned.</param>
        /// <param name="notifyIconData">The notify icon data returned.</param>
        /// <returns></returns>
        private static bool GetTBButton_WoW64(IntPtr hToolbar, int i, ref string iconText, NotifyIconData_WoW64 notifyIconData)
        {
            // One page
            const int BUFFER_SIZE = 0x1000;

            UInt32 processId;
            UInt32 threadId = User32.GetWindowThreadProcessId(hToolbar, out processId);

            IntPtr hProcess = Kernel32.OpenProcess(ProcessRights.ALL_ACCESS, false, processId);
            if (hProcess == IntPtr.Zero)
            {
                Debug.Print("OpenProcess failed.");
                return false;
            }

            IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx(
                hProcess,
                IntPtr.Zero,
                new UIntPtr(BUFFER_SIZE),
                MemAllocationType.COMMIT,
                MemoryProtection.PAGE_READWRITE);

            if (ipRemoteBuffer == IntPtr.Zero)
            {
                Debug.Print("VirtualAllocEx failed.");
                return false;
            }

            // get buttons
            int bb = (int)User32.SendMessage(hToolbar, TB.GETBUTTON, (IntPtr)i, ipRemoteBuffer);
            if (bb == 0)
            {
                Debug.Print("SendMessage TB.GETBUTTON");
                return (false);
            }

            TBBUTTON_WoW64 tb64 = new TBBUTTON_WoW64();
            Int32 size = tb64.Size();
            IntPtr tb64Buffer = Marshal.AllocHGlobal(size);
            IntPtr bytesRead = Marshal.AllocHGlobal(4);

            bool bb2 = Kernel32.ReadProcessMemory(
                hProcess,
                ipRemoteBuffer,
                tb64Buffer,
                new UIntPtr((uint)size),
                bytesRead);

            if (!bb2)
            {
                Debug.Print("ReadProcessMemory");
                return false;
            }

            Marshal.PtrToStructure(tb64Buffer, tb64);
            if (tb64Buffer != IntPtr.Zero)
            {
                // Free the memory allocated previously by AllocHGlobal.
                Marshal.FreeHGlobal(tb64Buffer);
            }

            if (bytesRead != IntPtr.Zero)
            {
                // Free the memory allocated previously by AllocHGlobal.
                Marshal.FreeHGlobal(bytesRead);
            }

            // button text

            int textCharCount = (int)User32.SendMessage(hToolbar, TB.GETBUTTONTEXTW, (IntPtr)tb64.idCommand, ipRemoteBuffer);
            if (textCharCount == -1)
            {
                Debug.Print("SendMessage TB.GETBUTTONTEXTW");
                return false;
            }

            IntPtr charBuffer = Marshal.AllocHGlobal(BUFFER_SIZE);
            bytesRead = Marshal.AllocHGlobal(4);

            bool bb4 = Kernel32.ReadProcessMemory(
                 hProcess,
                 ipRemoteBuffer,
                 charBuffer,
                 new UIntPtr(BUFFER_SIZE),
                 bytesRead);

            if (!bb4)
            {
                Debug.Print("ReadProcessMemory button text");
                return false;
            }


            iconText = Marshal.PtrToStringUni(charBuffer, textCharCount);
            if (iconText == " ") iconText = String.Empty;

            if (charBuffer != IntPtr.Zero)
            {
                // Free the memory allocated previously by AllocHGlobal.
                Marshal.FreeHGlobal(charBuffer);
            }

            if (bytesRead != IntPtr.Zero)
            {
                // Free the memory allocated previously by AllocHGlobal.
                Marshal.FreeHGlobal(bytesRead);
            }


            IntPtr nidData = Marshal.AllocHGlobal(notifyIconData.Size());
            IntPtr remoteData = new IntPtr(tb64.dwData);
            bytesRead = Marshal.AllocHGlobal(4);

            bool bbb4 = Kernel32.ReadProcessMemory(
                hProcess,
                remoteData,
                nidData,
                new UIntPtr((uint)notifyIconData.Size()),
                bytesRead);

            if (!bbb4)
            {
                Debug.Print("ReadProcessMemory dwData");
                return false;
            }

            Marshal.PtrToStructure(nidData, notifyIconData);

            if (nidData != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(nidData);
            }

            if (bytesRead != IntPtr.Zero)
            {
                // Free the memory allocated previously by AllocHGlobal.
                Marshal.FreeHGlobal(bytesRead);
            }

            Kernel32.VirtualFreeEx(
                hProcess,
                ipRemoteBuffer,
                UIntPtr.Zero,
                MemAllocationType.RELEASE);

            Kernel32.CloseHandle(hProcess);

            return true; 
        }

        #endregion

        #region External Methods
/*
 * ShowWindow() Commands
 */
        private const int SW_HIDE = 0;
        private const int SW_SHOWNORMAL = 1;
        private const int SW_NORMAL = 1;
        private const int SW_SHOWMINIMIZED = 2;
        private const int SW_SHOWMAXIMIZED = 3;
        private const int SW_MAXIMIZE = 3;
        private const int SW_SHOWNOACTIVATE = 4;
        private const int SW_SHOW = 5;
        private const int SW_MINIMIZE = 6;
        private const int SW_SHOWMINNOACTIVE = 7;
        private const int SW_SHOWNA = 8;
        private const int SW_RESTORE = 9;
        private const int SW_SHOWDEFAULT = 10;
        private const int SW_FORCEMINIMIZE = 11;
        private const int SW_MAX = 11;

        private const UInt32 WM_LBUTTONDBLCLK = 0x0203;

        /// <summary>
        /// EXTERN
        /// The SwitchToThisWindow function is called to switch focus to a specified window and bring it to the foreground.
        /// </summary>
        /// <param name="hWnd">Handle to the window being switched to.</param>
        /// <param name="fAltTab">A TRUE for this parameter indicates that the window is being switched to using the Alt/Ctl+Tab key sequence. This parameter should be FALSE otherwise.</param>
        [DllImport("user32.dll", SetLastError = true)]
        private static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);

        /// <summary>
        /// The ShowWindowAsync function sets the show state of a window created by a different thread.
        /// </summary>
        /// <param name="hWnd">Handle to the window.</param>
        /// <param name="swCommand">Specifies how the window is to be shown. For a list of possible values, see the description of the ShowWindow function.</param>
        /// <returns></returns>
        [DllImport("User32.dll")]
        private static extern int ShowWindowAsync(IntPtr hWnd, int swCommand);

        /// <summary>
        /// The IsWindowVisible function retrieves the visibility state of the specified window.
        /// </summary>
        /// <param name="hwnd">Handle to the window to test.</param>
        /// <returns>
        /// If the specified window, its parent window, its parent's parent window, and so forth, have the WS_VISIBLE style, the return value is nonzero. 
        /// Otherwise, the return value is zero. Because the return value specifies whether the window has the WS_VISIBLE style, it may be nonzero even if 
        /// the window is totally obscured by other windows.
        /// </returns>
        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool IsWindowVisible(IntPtr hwnd);

        #endregion
    }
}
